package com.skivingcloud.apiplatform.signature.service;

import com.skivingcloud.apiplatform.contants.CryptContant;
import com.skivingcloud.apiplatform.mng.entity.ApiPlatformMng;
import com.skivingcloud.apiplatform.mng.service.IApiPlatformMngService;
import com.skivingcloud.apiplatform.signature.annotation.*;
import com.skivingcloud.apiplatform.signature.exception.SignedException;
import com.skivingcloud.apiplatform.signature.util.CryptoRedisUtil;
import com.skivingcloud.apiplatform.signature.util.IpWhiteCheckUtil;
import com.skivingcloud.apiplatform.signature.util.SmCryptoUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.web.subject.WebSubject;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

@Aspect
@Component
@Primary
public class BaseSignedService {

    @Value("${skivingcloud.api.signature.time-diff-max:10}")
    private long TIME_DIFF_MAX;
    private static final String PREFIX = "skivingcloud:api:signature:";
    @Autowired(required = false)
    private CryptoRedisUtil cryptoRedisUtil;
    @Autowired
    private IApiPlatformMngService apiPlatformMngService;
    @Autowired
    private Map<String,ExtendCryptService> extendCryptBeanMap;

    @Pointcut("@within(com.skivingcloud.apiplatform.signature.annotation.SignedMapping) || @annotation(com.skivingcloud.apiplatform.signature.annotation.SignedMapping) ")
    public void mapping() {
    }

    @Before("mapping() ")
    public void before(JoinPoint joinPoint) throws Exception {
        MethodSignature sign = (MethodSignature) joinPoint.getSignature();
        Method method = sign.getMethod();

        //  获取类或方法注解和参数
        SignedMapping signedMapping = AnnotationUtils.findAnnotation(method, SignedMapping.class);
        if (signedMapping == null) {
            signedMapping = AnnotationUtils.findAnnotation(joinPoint.getTarget().getClass(), SignedMapping.class);
        }
        Class clazz = signedMapping.value();
        //  如果有自定义实现类, prevent multiple executions here
        if (!this.getClass().equals(clazz)) {
            return;
        }

        Object[] args = joinPoint.getArgs();
        for (Object obj : args) {
            SignedEntity signedEntity = AnnotationUtils.findAnnotation(obj.getClass(), SignedEntity.class);
            if (signedEntity != null) {
                entry(obj);
                break;
            } else {
                continue;
            }
        }
    }

    /**
     * 白名单校验
     * @param request
     */
    private void checkWhiteList(HttpServletRequest request, String whiteList) {
        try {
            String ip = IpWhiteCheckUtil.getIpAddr(request);
            if(!IpWhiteCheckUtil.isPermited(ip, whiteList)){
                throw new SignedException.WhiteListError("你的ip地址" + ip + "不在白名单内");
            }
        }catch (SignedException.WhiteListError ee) {
            throw ee;
        }catch (Exception ee) {
            throw new SignedException.WhiteListError("无法获取ip地址");
        }
    }

    /**
     * 验签入口方法，验证加密参数的合法性（appid是否存在、是否重复提价、是否被篡改。。。。）
     * @param obj
     * @throws Exception
     */
    public void entry(Object obj) throws Exception {
        Map map = object2Map(obj);
        // 从参数中获取appid
        String appId = (String) getParamByAnnotation(obj, SignedAppId.class);
        // 获取时间戳
        long timestamp = (Long) getParamByAnnotation(obj, SignedTimestamp.class);
        // 获取随机数
        int nonce = (Integer) getParamByAnnotation(obj, SignedNonce.class);
        // 获取加密数据
        String signatureParam = (String) getParamByAnnotation(obj, Signature.class);

        isTimeDiffLarge(timestamp);
        //  按照ASCII对map进行排序
        SortedMap<String, Object> sortedMap = new TreeMap(map);
        ApiPlatformMng mng = apiPlatformMngService.selectByAppId(appId);
        HttpServletRequest request = (HttpServletRequest) ((WebSubject) SecurityUtils.getSubject()).getServletRequest();
        checkWhiteList(request, mng.getWhiteList());
        switch(mng.getEncryption()){
            case CryptContant.CRYPT_ALGORITHM_SM2:
                if(SmCryptoUtils.sm2Verify(signatureParam, sortedMap, mng.getAppPrivateSecret())){
                    System.out.println("sm2验签成功");
                }else{
                    throw new SignedException.SignatureError(signatureParam);
                }
                break;
            case CryptContant.CRYPT_ALGORITHM_SM3:
                if(SmCryptoUtils.sm3Verify(signatureParam, sortedMap, mng.getAppSecret())){
                    System.out.println("sm3验签成功");
                }else{
                    throw new SignedException.SignatureError(signatureParam);
                }
                break;
            case CryptContant.CRYPT_ALGORITHM_SM4:
                if(SmCryptoUtils.sm4Verify(signatureParam, sortedMap, mng.getAppSecret())){
                    System.out.println("sm4验签成功");
                }else{
                    throw new SignedException.SignatureError(signatureParam);
                }
                break;
            case CryptContant.CRYPT_ALGORITHM_AES128:
            case CryptContant.CRYPT_ALGORITHM_AES256:
                if(SmCryptoUtils.aesVerify(signatureParam, sortedMap, mng.getAppSecret())){
                    System.out.println("aes验签成功");
                }else{
                    throw new SignedException.SignatureError(signatureParam);
                }
                break;
            case CryptContant.CRYPT_ALGORITHM_MD5:
                if(SmCryptoUtils.md5Verify(signatureParam, sortedMap, mng.getAppSecret())){
                    System.out.println("MD5验签成功");
                }else{
                    throw new SignedException.SignatureError(signatureParam);
                }
                break;
            case CryptContant.CRYPT_ALGORITHM_SHA256:
                if(SmCryptoUtils.sha256Verify(signatureParam, sortedMap, mng.getAppSecret())){
                    System.out.println("SHA256验签成功");
                }else{
                    throw new SignedException.SignatureError(signatureParam);
                }
                break;
            case CryptContant.CRYPT_ALGORITHM_SHA512:
                if(SmCryptoUtils.sha512Verify(signatureParam, sortedMap, mng.getAppSecret())){
                    System.out.println("SHA512验签成功");
                }else{
                    throw new SignedException.SignatureError(signatureParam);
                }
                break;
            case CryptContant.CRYPT_ALGORITHM_RSA:
                if(SmCryptoUtils.rsaVerify(signatureParam, sortedMap, mng.getAppPrivateSecret())){
                    System.out.println("RSA验签成功");
                }else{
                    throw new SignedException.SignatureError(signatureParam);
                }
                break;
            case CryptContant.CRYPT_ALGORITHM_EXTEND:
                ExtendCryptService extendCryptService = extendCryptBeanMap.get(mng.getEncBean());
                if(extendCryptService.verify(signatureParam, sortedMap, mng.getAppSecret())){
                    System.out.println("自定义验签成功");
                }else{
                    throw new SignedException.SignatureError(signatureParam);
                }
                break;
            default:
                if(mng.getEncryption().startsWith("Hmac")){
                    if(SmCryptoUtils.hmacVerify(mng.getEncryption(),signatureParam, sortedMap, mng.getAppPrivateSecret())){
                        System.out.println("Hmac验签成功");
                    }else{
                        throw new SignedException.SignatureError(signatureParam);
                    }
                }else {
                    throw new SignedException.SignatureError(signatureParam);
                }
                break;
        }
    }

    /**
     * 将非空参数转换为map
     *
     * @param obj
     * @return
     * @throws IllegalAccessException
     */
    public Map<String, Object> object2Map(Object obj) throws IllegalAccessException {
        Map<String, Object> map = new HashMap();
        if (obj == null) {
            return map;
        }
        Class clazz = obj.getClass();
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            //  If the field is not ignored, put it in the map
            //  If the field is null, throw an exception
            SignedIgnore signedIgnore = AnnotationUtils.findAnnotation(field, SignedIgnore.class);
            if (signedIgnore == null) {
                if (field.get(obj) == null) {
                    throw new SignedException.NullParam(field.getName());
                }
                map.put(field.getName(), field.get(obj));
            }
        }
        return map;
    }

    /**
     * 获取注解修饰的参数字段
     *
     * @param obj
     * @param annotationType
     * @return
     * @throws IllegalAccessException
     */
    public final Object getParamByAnnotation(Object obj, Class<? extends Annotation> annotationType) throws IllegalAccessException {
        Class clazz = obj.getClass();
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            Annotation annotation = AnnotationUtils.findAnnotation(field, annotationType);
            if (annotation != null) {
                return field.get(obj);
            }
        }
        return null;
    }

    /**
     * 如果时间戳与系统时间相差过大时过不去
     *
     * @param timestamp
     */
    public void isTimeDiffLarge(long timestamp) {
        long diff = timestamp - System.currentTimeMillis() / 1000;
        if (Math.abs(diff) > TIME_DIFF_MAX) {
            throw new SignedException.TimestampError(diff + "");
        }
    }

    /**
     * 通过随机数和时间戳防重放攻击
     * @param appId
     * @param timestamp
     * @param nonce
     * @param signature
     */
    public void isReplayAttack(String appId, long timestamp, int nonce, String signature) {
        String key = PREFIX + appId + "_" + timestamp + "_" + nonce;
        Object obj = cryptoRedisUtil.get(key);
        if (obj != null && signature.equals(obj.toString()))
            throw new SignedException.ReplayAttack(appId, timestamp, nonce);
        else
            cryptoRedisUtil.set(key, signature, TIME_DIFF_MAX);
    }

    public String getSignature(String appId, Map map) throws NoSuchAlgorithmException, InvalidKeyException {
        ApiPlatformMng mng = apiPlatformMngService.selectByAppId(appId);
        if(mng == null)
            throw new SignedException.NullParam(appId);
        //  按照ASCII对map进行排序
        SortedMap<String, Object> sortedMap = new TreeMap(map);
        //  拼接参数
        //  e.g. "key1=value1&key2=value2"
        StringBuffer plainText = new StringBuffer();
        for (Map.Entry<String, Object> entry : sortedMap.entrySet()) {
            plainText.append(entry.getKey() + "=" + entry.getValue());
            plainText.append("&");
        }
        plainText.deleteCharAt(plainText.length() - 1);

        //根据加密方式mng.getEncryption()对plainText进行加密
        return null;
    }
}
